﻿namespace SuperModel.Generation
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Reflection.Emit;
    using BehaviorModel;
    using Extensions;

    /// <summary>
    /// An attribute transmitter
    /// </summary>
    public class AttributeTransmitter
    {
        /// <summary>
        /// Apply attributes to properties
        /// </summary>
        /// <param name="member">
        /// The member.
        /// </param>
        /// <param name="newProperty">
        /// The new member.
        /// </param>
        public void ApplyAttributesToProperty(MemberInfo member, PropertyBuilder newProperty)
        {
            // Gets the attributes on the source member
            IEnumerable<Attribute> attributes = member.GetCustomAttributes(false).Cast<Attribute>();

            foreach (Attribute attribute in attributes.Where(attribute => !typeof(PropertyInterceptionAttributeBase).IsAssignableFrom(attribute.GetType())))
            {
                CustomAttributeBuilder customAttributeBuilder = GetAttributeBuilder(attribute);
                newProperty.SetCustomAttribute(customAttributeBuilder);
            }
        }

        /// <summary>
        /// Apply attributes to a type
        /// </summary>
        /// <param name="typeToImplement">
        /// The type to implement.
        /// </param>
        /// <param name="newType">
        /// The new type.
        /// </param>
        public void ApplyAttributesToType(Type typeToImplement, TypeBuilder newType)
        {
            IList<Attribute> typeAttributes = typeToImplement.FindAllAttributesDeep();
            foreach (Attribute attribute in typeAttributes)
            {
                var customAttributeBuilder = GetAttributeBuilder(attribute);
                newType.SetCustomAttribute(customAttributeBuilder);
            }
        }

        /// <summary>
        /// Gets an Attribute builder for an attribute
        /// </summary>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        /// <returns>
        /// The attribute builder.
        /// </returns>
        private static CustomAttributeBuilder GetAttributeBuilder(Attribute attribute)
        {
            Type attributeType = attribute.GetType();
            ConstructorInfo defaultConstructor = attributeType.GetConstructor(Type.EmptyTypes);
            PropertyInfo[] attributeProperties = attributeType.GetProperties().Where(prop => prop.CanRead && prop.CanWrite).ToArray();
            object[] attributePropertyValues = GetPropertyValues(attribute, attributeProperties);

            return new CustomAttributeBuilder(defaultConstructor, new object[] { }, attributeProperties, attributePropertyValues);
        }

        /// <summary>
        /// Gets property values on an object
        /// </summary>
        /// <param name="targetObject">
        /// The target object.
        /// </param>
        /// <param name="attributeProperties">
        /// The attribute properties.
        /// </param>
        /// <returns>
        /// The property values.
        /// </returns>
        private static object[] GetPropertyValues(object targetObject, IEnumerable<PropertyInfo> attributeProperties)
        {
            var objects = new object[attributeProperties.Count()];

            var i = 0;
            foreach (PropertyInfo property in attributeProperties)
            {
                objects[i++] = property.GetValue(targetObject, new object[] { });
            }

            return objects;
        }
    }
}
